package ws.jdbc.concurrent.driver.sqlserver.tds.rpc;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Date;
import ws.jdbc.concurrent.driver.sqlserver.tds.datatypes.BYTELEN;
import ws.jdbc.concurrent.driver.sqlserver.tds.datatypes.FIXEDLENTYPE;
import ws.jdbc.concurrent.driver.sqlserver.tds.datatypes.TYPE_INFO;
import ws.jdbc.concurrent.driver.sqlserver.tds.datatypes.TYPE_VARBYTE;
import ws.jdbc.concurrent.driver.sqlserver.tds.datatypes.TypeInfoVarLenType;
import ws.jdbc.concurrent.driver.sqlserver.tds.datatypes.VARLENTYPE;
import ws.utils.ArrayUtil;

/**
 * Update value parameter for the 'sp_cursor' stored procedure.
 * 
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class UpdateValue extends ParameterData {

  private final byte[] columnName;
  private final byte columnNameLength;
  private final TYPE_INFO typeInfo;
  /** Actual value. */
  private TYPE_VARBYTE value;
  /** {@code UpdateValue} represented as byte array. */
  private byte[] data;

  /**
   * Create a new instance.
   *
   * @param count number of columns on the row.
   */
  public UpdateValue(final String columnName, final TYPE_INFO typeInfo) {
    if(typeInfo.getDataType()==FIXEDLENTYPE.INT4TYPE) {
      this.typeInfo = new TypeInfoVarLenType(VARLENTYPE.INTTYPE, new BYTELEN(4));
    } else {
      this.typeInfo = typeInfo;
    }
    this.columnName = ArrayUtil.toLittleEndian("@" + columnName);
    this.columnNameLength = (byte) (columnName.length() + 1);
  }

  public void setValue(int columnValue) {
    value = new TYPE_VARBYTE(columnValue);
  }

  public void setValue(String columnValue) {
    value = new TYPE_VARBYTE(columnValue);
  }

  public void setValue(double columnValue) {
    value = new TYPE_VARBYTE(columnValue);
  }

  public void setValue(Date columnValue) {
    int days = (int) (columnValue.getTime() / 86400000 + 25567);
    int seconds = (int) ((columnValue.getTime() % 86400000) * 300 / 1000);
    ByteBuffer buffer = ByteBuffer.allocate(8).
            order(ByteOrder.LITTLE_ENDIAN).
            putInt(0, days).
            putInt(4, seconds);

    value = new TYPE_VARBYTE(typeInfo.getTypeVarLen(), buffer.array());
  }

  public TYPE_VARBYTE getValue() {
    return value;
  }

  /**
   * Clear the current update values.
   * 
   * All previously created values will be set to {@code NULL}.
   */
  public void clear() {
    value = null;
    data = null;
  }

  /**
   * Get the byte representation of this update value.
   *
   * @return byte representation of this update value.
   */
  @Override
  public byte[] getBytes() {
    if (data == null) {
      setData();
    }
    return data;
  }

  /**
   * Get the number of bytes in byte representation.
   *
   * @return number of bytes.
   */
  @Override
  public int size() {
    if (data == null) {
      setData();
    }
    return data.length;
  }

  private void setData() {
    if (value == null) {
      data = new byte[0];
    } else {
      data = ByteBuffer.allocate(columnName.length + 2 + typeInfo.size() + value.size()).
              order(ByteOrder.LITTLE_ENDIAN).
              put(columnNameLength).
              put(columnName).
              put((byte) 0x0).
              put(typeInfo.getBytes()).
              put(value.getBytes()).
              array();
    }
  }
}
